#include "stdafx.h"
#include "RWFile.h"
#include "LzmaLib.h"
#include "FileUtils.h"
#include <vector>
#include <string>
#include <algorithm>
#include <dbghelp.h>
#include <shlwapi.h>
#pragma comment(lib, "dbghelp")
#pragma comment(lib, "shlwapi")

using namespace std;
using namespace FileUtils;

struct CfsFileHdr
{
	CfsFileHdr()
	{
		mark = 0x0A736663;
		mark1 = 0x0D020801;
		version = 0x00000002;
		count = 0x00000000;
		empty = 0x00000000;
	}
	DWORD mark;
	DWORD mark1;
	DWORD version;
	DWORD count;
	DWORD empty;
};

struct CfsFileInfo
{
	string filename;
	DWORD fileSize;
	DWORD streamSize;
	DWORD streamPos;
	//--------------------
	BYTE prop[5];
	string streamdata;
	//--------------------
	string filedata;
	string filename_full;
};
CfsFileHdr g_cfsHdr;
void DecCFS(string path)
{
	string dir = path.substr(0, path.find_last_of('.')) + "\\";
	::MakeSureDirectoryPathExists(dir.c_str());
	string kBuff;
	if (!RWFile::LoadFile(path, kBuff))
	{
		return;
	}
	int len = kBuff.length();
	if (len <= sizeof(CfsFileHdr))
	{
		return;
	}
	BYTE* start = (BYTE*)kBuff.c_str();
	BYTE* stream_ptr = start;
	CfsFileHdr* hdr = (CfsFileHdr*)stream_ptr;
	if (g_cfsHdr.mark != hdr->mark ||
		g_cfsHdr.mark1 != hdr->mark1 ||
		g_cfsHdr.version != hdr->version)
	{
		return;
	}
	DWORD item_count = hdr->count;
	stream_ptr += sizeof(CfsFileHdr);

	for (DWORD idx = 0; idx < item_count; idx++)
	{
		CfsFileInfo cfs_item;
		DWORD name_len = *(DWORD*)stream_ptr;
		stream_ptr += 4;
		string name;
		for (DWORD i = 0; i < name_len; i++)
		{
			name.append(1, *stream_ptr++ ^ 0x3A);
		}
		cfs_item.filename = name;
		cfs_item.fileSize = *(DWORD*)stream_ptr;
		stream_ptr += 4;
		cfs_item.streamSize = *(DWORD*)stream_ptr;
		stream_ptr += 4;
		cfs_item.streamPos = *(DWORD*)stream_ptr;
		stream_ptr += 4;


		BYTE* item_data = start + cfs_item.streamPos;
		for (DWORD i = 0; i < cfs_item.streamSize; i++)
		{
			cfs_item.streamdata.append(1, *item_data++);
		}
		cfs_item.filedata.resize(cfs_item.fileSize);
		size_t dstLen = cfs_item.fileSize;
		size_t srcLen = cfs_item.streamSize - 5;
		const unsigned char *props = (const unsigned char *)cfs_item.streamdata.c_str();
		size_t propsSize = 5;
		int ret = LzmaUncompress((BYTE*)cfs_item.filedata.c_str(), &dstLen, props + 5, &srcLen, props, propsSize);
		if (SZ_OK == ret)
		{
			printf("Dec: %s OK\r\n", cfs_item.filename.c_str());
			RWFile::SaveFile(dir + cfs_item.filename, cfs_item.filedata);
		}
	}
	printf("DecCFS: %s OK\r\n", path.c_str());
}
void EncCFS(string path)
{
	if (path[path.length()-1] == '\\')
	{
		path.resize(path.length() - 1);
	}
	string cfs_file_name = path.substr(path.find_last_of('\\') + 1) + ".cfs";

	vector<file_t> files;
	SearchFiles(path.c_str(), files);
	if (files.empty())
	{
		return;
	}
	DWORD all_name_len = 0;
	vector<CfsFileInfo> cfs_items;
	for (size_t i = 0; i < files.size(); i++)
	{
		file_t& kfile = files[i];
		if (kfile.isDir)
		{
			continue;
		}
		CfsFileInfo cfs_item = {};
		cfs_item.filename = kfile.filename;
		cfs_item.fileSize = (DWORD)kfile.filesize;
		cfs_item.filename_full = kfile.fullpath;
		all_name_len += cfs_item.filename.length();
		cfs_items.push_back(cfs_item);
	}
	if (cfs_items.empty())
	{
		return;
	}
	DWORD streamPos = sizeof(CfsFileHdr) + cfs_items.size() * 4 + cfs_items.size() * 12 + all_name_len;
	string kBuff;
	for (size_t i = 0; i < cfs_items.size(); i++)
	{
		CfsFileInfo& cfs_item = cfs_items[i];
		cfs_item.streamPos = streamPos;
		if (!RWFile::LoadFile(cfs_item.filename_full, kBuff))
		{
			printf("LoadFile: %s ERR\r\n", cfs_item.filename.c_str());
			return;
		}
		int len = kBuff.length();
		BYTE* src = (BYTE*)kBuff.c_str();
		cfs_item.streamdata.resize(len * 5);
		BYTE* dst = (BYTE*)cfs_item.streamdata.c_str();

		size_t sizeProp = 5;
		size_t dstLen = cfs_item.streamdata.length();
		size_t srcLen = len;
		if (SZ_OK == LzmaCompress(dst, &dstLen, src, srcLen, cfs_item.prop, &sizeProp, 9, (1 << 24), 3, 0, 2, 32, 2))
		{
			printf("Enc: %s OK\r\n", cfs_item.filename.c_str());
		}
		else
		{
			printf("Enc: %s ERR\r\n", cfs_item.filename.c_str());
			return;
		}
		cfs_item.streamdata.resize(dstLen);
		cfs_item.streamSize = dstLen;
		streamPos += sizeof(cfs_item.prop) + dstLen;
		size_t name_len = cfs_item.filename.size();
		for (size_t i = 0; i < name_len; i++)
		{
			cfs_item.filename[i] ^= 0x3A;
		}
	}
	string kBuffSave;
	CfsFileHdr hdr;
	hdr.count = cfs_items.size();
	kBuffSave.append((char*)&hdr, sizeof(CfsFileHdr));
	for (size_t i = 0; i < cfs_items.size(); i++)
	{
		CfsFileInfo& cfs_item = cfs_items[i];
		int len = cfs_item.filename.length();
		kBuffSave.append((char*)&len, sizeof(DWORD));
		kBuffSave += cfs_item.filename;
		DWORD streamSize = cfs_item.streamSize + sizeof(cfs_item.prop);
		kBuffSave.append((char*)&cfs_item.fileSize, sizeof(DWORD));
		kBuffSave.append((char*)&streamSize, sizeof(DWORD));
		kBuffSave.append((char*)&cfs_item.streamPos, sizeof(DWORD));
	}
	for (size_t i = 0; i < cfs_items.size(); i++)
	{
		CfsFileInfo& cfs_item = cfs_items[i];
		kBuffSave.append((char*)&cfs_item.prop, sizeof(cfs_item.prop));
		kBuffSave += cfs_item.streamdata;
	}
	printf("EncCFS: %s OK\r\n", cfs_file_name.c_str());
	RWFile::SaveFile(path + ".cfs", kBuffSave);
}
int main(int argc, char **argv)
{
	printf("DecCFS v1.0 by zhupf\r\n");
	if (argc == 2)
	{
		string path = argv[1];
		if (!PathFileExists(path.c_str()))
		{
			printf("file not found!!!");
			return -1;
		}
		if (PathIsDirectory(path.c_str()))
		{
			printf("EncCFS: %s START\r\n", path.c_str());
			EncCFS(path);
		}
		else
		{
			printf("DecCFS: %s START\r\n", path.c_str());
			DecCFS(path);
		}
	}
    return 0;
}